<?php

abstract class Kd_Model_Entity extends Kd_Object {

    protected static $initializedTables = array();
    protected $originalData = array();
    private $_table = NULL;

    public function __construct() {
        parent::__construct();
        if (!isset(self::$initializedTables[get_class($this)])) {
            $class = $this->getTableClass();
            $this->_table = new $class();
            $this->initializeTable($this->_table);
            self::$initializedTables[get_class($this)] = $this->_table;
        }
        $this->_table = self::$initializedTables[get_class($this)];
//        foreach ($this->_table->getColumns() as $column) {
//            if ($column->getDefaultValue() !== NULL) {
//                $this->data[$column->getColumnName()] = $column->getDefaultValue();
//            }
//        }
    }

    protected abstract function initializeTable($table);

    protected function getTableClass() {
        return 'Kd_Data_Table';
    }

    public function getDbDriver($transaction = NULL) {
        return is_null($transaction) ? Kd_Db_Factory::getDriver() : $transaction->getDbDriver();
    }

    public function getTable() {
        return $this->_table;
    }

    public function markNoChanged() {
        foreach ($this->getTable()->getColumns() as $column) {
            $this->originalData[$column->getColumnName()] = $this->getData($column->getColumnName());
        }
        return $this;
    }

    public function getIsChanged() {
        if ($this->isNew()) {
            return TRUE;
        }
        $changedData = $this->getChangedData();
        return!empty($changedData);
    }

    public function getOriginalData($name) {
        if (isset($this->originalData[$name])) {
            return $this->originalData[$name];
        }
        return NULL;
    }

    protected function compareArrayValue($a, $b){
        
    }

    protected function propertyIsChanged($name) {
        if ($this->getTable()->hasColumn($name) && $this->hasData($name)) {
            $value = $this->getData($name, NULL);
            $originalValue = $this->getOriginalData($name);
            if (is_array($value) && is_array($originalValue)) {
                if(!Kd_Util_Array::compareArray($value, $originalValue)){
                    return TRUE;
                }
            } elseif ($originalValue != $value) {
                return TRUE;
            }
        }
        return FALSE;
    }

    protected function getChangedData() {
        $result = array();
        foreach ($this->getTable()->getColumns() as $column) {
            if ($this->propertyIsChanged($column->getColumnName())) {
                $result[$column->getColumnName()] = $this->data[$column->getColumnName()];
            }
        }
        return $result;
    }

    public function getIdProperty() {
        return $this->getTable()->getIdColumn()->getColumnName();
    }

    public function getIdValue() {
        return $this->getData($this->getIdProperty(), NULL);
    }

    public function setIdValue($value) {
        $this->setData($this->getIdProperty(), $value);
        return $this;
    }

    public function isNew() {
        $id = $this->getIdValue();
        return empty($id);
    }

    public function validate() {
        return TRUE;
    }

    public function save($transaction = NULL) {
        $transaction = is_null($transaction) ? Kd_Db_Factory::getTransaction() : $transaction;
        try {
            $transaction->begin();
            $this->isNew() ? $this->insert($transaction) : $this->update($transaction);
            $transaction->commit();
            
        } catch (Exception $ex) {
            $transaction->rollback();
            throw $ex;
        }

        return $this;
    }

    public function insert($transaction) {
        $this->beforeInsert($transaction);
        $this->insertSelf($transaction);
        $this->afterInsert($transaction);
        $this->markNoChanged();
        return $this;
    }

    protected function insertSelf($transaction) {
        $this->getTable()->insert($this->getAllData(), $transaction);
        $idColumn = $this->getTable()->getIdColumn();
        if ($idColumn->getIsIncrement()) {
            $this->setIdValue($this->getTable()->getInsertId($transaction));
        }elseif($idColumn->getColumnType() == 'guid'){
            $this->setIdValue($idColumn->getInsertValue());
        }
    }

    protected function beforeInsert($transaction) {

    }

    protected function afterInsert($transaction) {

    }

    public function update($transaction) {
        $this->beforeUpdate($transaction);
        if ($this->getIsChanged()) {
            $this->updateSelf($transaction);
        }
        $this->afterUpdate($transaction);
        $this->markNoChanged();
        return $this;
    }

    protected function updateSelf($transaction) {
        $data = $this->getChangedData();
        $data[$this->getIdProperty()] = $this->getIdValue();
        $this->getTable()->update($data, $transaction);
    }

    protected function beforeUpdate($transaction) {
        
    }

    protected function afterUpdate($transaction) {
        
    }

    public function delete($transaction = NULL) {
        $transaction = is_null($transaction) ? Kd_Db_Factory::getTransaction() : $transaction;
        try {
            $transaction->begin();
            $this->beforeDelete($transaction);
            $this->getTable()->delete($this->getIdValue(), $transaction);
            $this->beforeUpdate($transaction);
            $transaction->commit();
        } catch (Exception $ex) {
            $transaction->rollback();
            throw $ex;
        }

        return $this;
    }

    protected function beforeDelete($transaction) {
        
    }

    protected function afterDelete($transaction) {
        
    }

    public function deleteBatch(array $ids, $transaction = NULL) {
        $transaction = is_null($transaction) ? Kd_Db_Factory::getTransaction() : $transaction;
        try {
            $transaction->begin();
            $this->getTable()->deleteBatch($ids, $transaction);
            $transaction->commit();
        } catch (Exception $ex) {
            $transaction->rollback();
            throw $ex;
        }

        return $this;
    }

    public function fill($data) {
        if (is_array($data) && count($data) > 0) {
            foreach ($data as $key => $value) {
                $convertedValue = $value;
                if ($this->getTable()->hasColumn($key)) {
                    $convertedValue = $this->getTable()->getColumn($key)->convertFromDb($value);   
                }
                $this->setData($key, $convertedValue);
            }
        }   
        return $this;
    }

    public function load($id) {
        $row = $this->getTable()->getById($id);
        if (!empty($row)) {
            $this->fill($row)->markNoChanged();
        }
        return $this;
    }

    public function equal($obj) {
        if (empty($obj)) {
            return FALSE;
        }
        if (get_class($obj) != get_class($this)) {
            return FALSE;
        }
        return $this->equalId($obj->getIdValue());
    }

    public function equalId($id) {
        $meId = $this->getIdValue();
        if (empty($meId)) {
            return FALSE;
        }

        return strcmp($meId, $id) == 0;
    }

}